/*
 * Copyright 2011-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.mgmt.framework.concurrent.distlock.consul;

import java.nio.charset.StandardCharsets;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.OperationException;
import com.ecwid.consul.v1.kv.model.PutParams;
import com.ecwid.consul.v1.session.model.NewSession;
import com.ecwid.consul.v1.session.model.Session;

/**
 * 基于 Consul 的分布式锁
 *
 * @author liym
 * @since 2020-01-16 11:05 新建
 */
public final class DistributedLock {

    /**
     * 分布式锁标识前缀
     *
     * @since 2019-04-11 18:39
     */
    private static final String LOCK_PREFIX = "distlock/";

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final ConsulClient client;

    private final String lockKey;

    private String sessionId;

    /**
     * 构造分布式锁
     *
     * @param consulClient Consul 客户端
     * @param key 锁定变量的名称或类型等，如 product（产品）、user（用户）等
     */
    public DistributedLock(ConsulClient consulClient, String key) {
        this.client = consulClient;
        this.lockKey = LOCK_PREFIX + key;
    }

    /**
     * 获取分布式锁
     *
     * @param x 锁定变量的值，如线程ID、商品ID等
     * @param timeout 锁的超期时间（秒）：10s到86400s之间
     * @since 2020-01-16 13:58:57
     */
    public void lock(String x, long timeout) {
        while (true) {
            if (this.tryLock(x, timeout)) {
                return;
            }
        }
    }

    /**
     * 尝试获取分布式锁
     *
     * @param x 锁定变量的值，如线程ID、商品ID等
     * @param timeout 锁的超期时间（秒）：10s到86400s之间
     * @return 加锁是否成功：true/false
     * @since 2020-01-16 11:14
     */
    public boolean tryLock(String x, long timeout) {
        PutParams putParams = new PutParams();

        Boolean result;
        try {
            this.sessionId = this.createSession(this.client, this.lockKey, timeout);
            putParams.setAcquireSession(this.sessionId);
            result = this.client.setKVValue(this.lockKey, x, putParams).getValue();
        } catch (OperationException e) {
            result = null;
            this.logger.error("tryLock error", e);
        }

        return result != null && result;
    }

    /**
     * 释放分布式锁
     *
     * @param x 锁定变量的值，如线程ID、商品ID等
     * @return 解锁是否成功：true/false
     * @since 2020-01-16 14:00:16
     */
    public boolean unlock(String x) {
        if (this.sessionId == null) {
            // throw new IllegalStateException("No lock");
            return false;
        }
        PutParams putParams = new PutParams();

        Boolean result;
        try {
            putParams.setReleaseSession(this.sessionId);
            result = this.client.setKVValue(this.lockKey, x, putParams).getValue();
            this.client.sessionDestroy(this.sessionId, null);
        } catch (OperationException e) {
            result = null;
            this.logger.error("unlock error", e);
        }

        return result != null && result;
    }

    /**
     * 判断锁是否被持有
     *
     * @param x 锁定变量的值，如线程ID、商品ID等
     * @return 锁是否被持有：true/false
     * @since 2020-05-15 13:56:15
     */
    public boolean isLocked(String x) {
        // TODO 测试
        String value;
        try {
            value = this.client.getKVValue(this.lockKey).getValue().getDecodedValue(StandardCharsets.UTF_8);
        } catch (OperationException e) {
            value = null;
            this.logger.error("isLocked error", e);
        }

        return x.equals(value);
    }

    /**
     * 创建 Session
     *
     * <p>https://www.consul.io/api/session.html#create-session</p>
     *
     * @param client Consul 客户端
     * @param sessionName Session 名称
     * @param ttl 锁的超期时间（秒）
     * @return Session ID（UUID）
     */
    private String createSession(ConsulClient client, String sessionName, long ttl) {
        NewSession session = new NewSession();
        session.setName(sessionName);
        // 当 Session 失效后，会被销毁，不能再使用。关联的锁的行为，取决于创建时指定的 Behavior：RELEASE（默认）、DELETE
        // RELEASE：任何与该 Session 相关的锁都会被释放，并且持有该锁的 Key 的 ModifyIndex 会递增。
        // DELETE：持有该锁的 Key 将会被删除。
        session.setBehavior(Session.Behavior.DELETE);
        session.setTtl(ttl + "s");
        return client.sessionCreate(session, null).getValue();
    }

}
